/**
 * Copyright (C) 2013-2014 the original author or authors.
 */
package com.foreveross.bsl.mongodb.repository.router;

import java.net.UnknownHostException;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.dao.DataAccessException;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoDbUtils;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.util.Assert;

import com.foreveross.bsl.common.utils.context.ContextThead;
import com.foreveross.bsl.mongodb.repository.handler.RouterScheme;
import com.mongodb.DB;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.MongoURI;
import com.mongodb.WriteConcern;

/**
 * @description
 *
 * @author rening
 * @version v1.0
 *
 * @2013-3-6
 *
 */
public class RouterMongodbFactory implements DisposableBean, MongoDbFactory {

	private final Mongo mongo;
	private final boolean mongoInstanceCreated;
	private final UserCredentials credentials;
	private WriteConcern writeConcern;
	
	private String defaultDatabaseName = "default_database"; //default database
	
	private RouterScheme routerScheme;
	
	/**
	 * Create an instance of {@link SimpleMongoDbFactory} given the {@link Mongo} instance and database name.
	 * 
	 * @param mongo Mongo instance, must not be {@literal null}.
	 * @param defaultDatabase database name, not be {@literal null}.
	 */
	public RouterMongodbFactory(Mongo mongo, String databaseName) {
		this(mongo, databaseName, UserCredentials.NO_CREDENTIALS, false);
	}

	/**
	 * Create an instance of SimpleMongoDbFactory given the Mongo instance, database name, and username/password
	 * 
	 * @param mongo Mongo instance, must not be {@literal null}.
	 * @param defaultDatabase Database name, must not be {@literal null}.
	 * @param credentials username and password.
	 */
	public RouterMongodbFactory(Mongo mongo, String databaseName, UserCredentials credentials) {
		this(mongo, databaseName, credentials, false);
	}

	/**
	 * Creates a new {@link SimpleMongoDbFactory} instance from the given {@link MongoURI}.
	 * 
	 * @param uri must not be {@literal null}.
	 * @throws MongoException
	 * @throws UnknownHostException
	 * @see MongoURI
	 */
	public RouterMongodbFactory(MongoURI uri) throws MongoException, UnknownHostException {
		this(new Mongo(uri), uri.getDatabase(), new UserCredentials(uri.getUsername(), parseChars(uri.getPassword())), true);
	}
	
	/**
	 * @param mongo
	 * @param defaultDatabase
	 * @param username
	 * @param password
	 * @param mongoInstanceCreated
	 * @param credentials
	 * @param writeConcern
	 */
	public RouterMongodbFactory(Mongo mongo, String databaseName, UserCredentials credentials, boolean mongoInstanceCreated) {
		Assert.notNull(mongo, "Mongo must not be null");
		Assert.hasText(databaseName, "database name must not be empty");
		Assert.isTrue(databaseName.matches("[\\w-]+"),
				"Database name must only contain letters, numbers, underscores and dashes!");
		this.mongo = mongo;
		this.defaultDatabaseName = databaseName;
		this.mongoInstanceCreated = mongoInstanceCreated;
		this.credentials = credentials == null ? UserCredentials.NO_CREDENTIALS : credentials;
//		routerScheme = new SimpleRouterScheme(credentials, writeConcern, this);
	}
	
	/**
	 * Configures the {@link WriteConcern} to be used on the {@link DB} instance being created.
	 * 
	 * @param writeConcern the writeConcern to set
	 */
	public void setWriteConcern(WriteConcern writeConcern) {
		this.writeConcern = writeConcern;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.mongodb.MongoDbFactory#getDb()
	 */
	public DB getDb() throws DataAccessException {
		String registerNo = ContextThead.getThreadVar(); // 获取租户名
		Class clazz = ContextThead.getThreadClass();
		if(registerNo == null || "".equals(registerNo)){
			DB db = MongoDbUtils.getDB(mongo, defaultDatabaseName, credentials);
			if (writeConcern != null) {
				db.setWriteConcern(writeConcern);
			}
			return db;
		}
		return getDb(registerNo, clazz);
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.mongodb.MongoDbFactory#getDb(java.lang.String)
	 */
	public DB getDb(String registerNo, Class clazz) throws DataAccessException {
		Assert.notNull(routerScheme, "router scheme is not init.");
		return routerScheme.getDb(registerNo, clazz);
	}

	/**
	 * Clean up the Mongo instance if it was created by the factory itself.
	 * 
	 * @see DisposableBean#destroy()
	 */
	public void destroy() throws Exception {
		if (mongoInstanceCreated) {
			mongo.close();
		}
	}
	
	private static String parseChars(char[] chars) {
		return chars == null ? null : String.valueOf(chars);
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.mongodb.MongoDbFactory#getDb(java.lang.String)
	 */
	@Override
	public DB getDb(String dbName) throws DataAccessException {
		String registerNo = ContextThead.getThreadVar();
		Class clazz = ContextThead.getThreadClass();
		if(registerNo == null || "".equals(registerNo)){
			DB db = MongoDbUtils.getDB(mongo, dbName, credentials);
			if (writeConcern != null) {
				db.setWriteConcern(writeConcern);
			}
			return db;
		}
		return routerScheme.getDb(registerNo, clazz, dbName);
	}

	/**
	 * @return the routerScheme
	 */
	public RouterScheme getRouterScheme() {
		return routerScheme;
	}

	/**
	 * @param routerScheme the routerScheme to set
	 */
	public void setRouterScheme(RouterScheme routerScheme) {
		this.routerScheme = routerScheme;
	}

	/**
	 * @return the mongo
	 */
	public Mongo getMongo() {
		return mongo;
	}

	/**
	 * @return the defaultDatabaseName
	 */
	public String getDefaultDatabaseName() {
		return defaultDatabaseName;
	}
	
}
